<?php

namespace nabu\controllers\userController;

defined('NABU') || exit;

require_once 'libs/csrf.php';
require_once 'libs/validations.php';
require_once 'models/userModel.php';
require_once 'libs/emails.php';
require_once 'libs/utils.php';

use nabu\libs\csrf\csrf,
    nabu\libs\validations\validations,
    nabu\models\userModel\userModel,
    nabu\libs\emails\emails,
    nabu\libs\utils\utils;

// Administra el flujo de datos de los usuarios.
class userController {
  private const password_cost = array('cost' => 12);
  private const function_hash = 'sha256';

  private static function default_route() {
    $GLOBALS['messages'] -> redirect(NABU_ROUTES['home']);
  }

  private static function errors(string $error) {
    $GLOBALS['messages'] -> errors('¡Lo sentimos mucho! &#x1F61E;, ' . $error . ', por favor inténtelo más tarde', 500);
  }

  // Renderiza la página web de registro de usuarios.
  public static function signup() {
    if (utils::session_exists())
      self::default_route();

    if (empty($_POST['signup_submit'])) {
      $head_title = 'Regístrate';
      $token      = csrf::generate_token();
      $messages   = $GLOBALS['messages'] -> messages();

      require_once 'views/layouts/head.php';
      require_once 'views/layouts/navbar.php';
      require_once 'views/pages/signup.php';
      require_once 'views/layouts/footer.php';
    }
    else {
      csrf::validate_token($_POST['csrf']);

      self::user_signup($_POST);
    }
  }

  // Activa una cuenta de usuario desde una URL de verificación de e-mail.
  public static function verifications() {
    if (empty($_GET['user']) || empty($_GET['key']))
      self::default_route();

    $userModel = new userModel();

    // Busca el usuario.
    $user = $userModel -> find_verifications($_GET['user']);

    if ($user === false)
      self::default_route();

    if (empty($user['token']) || empty($user['token_expiration']))
      self::default_route();

    // Reconstruye el token con la llave de verificación obtenida.
    $token = hash_hmac(self::function_hash, $user['email'], $_GET['key']);

    // Valida la autentificación del token.
    if (!hash_equals($user['token'], $token))
      self::default_route();

    global $messages;

    // Valida si el token está expirado y elimina el usuario.
    if (time() > $user['token_expiration']) {
      $userModel -> delete_user($user['id']);

      $messages -> add_message('Tu cuenta ha expirado, por favor vuelva a registrarse');
      $messages -> redirect(NABU_ROUTES['signup']);
    }

    // Activa la cuenta del usuario y crea su perfil.
    $userModel -> activate_account($user['id']);
    $userModel -> delete_verification($user['id']);
    $userModel -> create_profile($user['id']);

    $messages -> add_message('Tu dirección de correo electrónico se ha verificado correctamente');
    $messages -> redirect(NABU_ROUTES['login']);
  }

  // Renderiza la página web de acceso de usuarios.
  public static function login() {
    if (utils::session_exists())
      self::default_route();

    if (empty($_POST['login_submit'])) {
      $head_title = 'Inicar sesión';
      $token      = csrf::generate_token();
      $messages   = $GLOBALS['messages'] -> messages();

      require_once 'views/layouts/head.php';
      require_once 'views/layouts/navbar.php';
      require_once 'views/pages/login.php';
      require_once 'views/layouts/footer.php';
    }
    else {
      csrf::validate_token($_POST['csrf']);

      self::user_login($_POST);
    }
  }

  // Cierra la sesión de un usuario.
  public static function logout() {
    session_unset();
    session_destroy();

    self::default_route();
  }

  // Renderiza la página web de un perfil de usuario,
  // editar y eliminar perfil.
  public static function profile() {
    if (empty($_GET['user'])) {
      if (empty($_GET['page']))
        self::default_route();

      if ($_GET['page'] == 'edit')
        self::edit_profile();
      elseif ($_GET['page'] == 'delete')
        self::delete_profile();
      else
        self::default_route();
    }
    else {
      $userModel = new userModel();

      // Busca el perfil de un usuario.
      $user = $userModel -> find_profile('username', $_GET['user']);

      unset($userModel);

      if ($user === false)
        self::default_route();

      $head_title = 'Perfil';
      $token      = csrf::generate_token();

      // Escapa los caracteres del 'nombre completo' y el 'apodo' a HTML5.
      $user['name']     = utils::str_escape($user['name']);
      $user['username'] = utils::str_escape($user['username']);

      // Escapa la 'descripción o autobiografía' a HTML5.
      if (!empty($user['description']))
        $user['description'] = utils::str_escape($user['description']);
      else
        $user['description'] = 'Compartiendo conocimiento...';

      // Defina la ruta completa de la foto de perfil.
      if (empty($user['avatar']))
        $user['avatar'] = NABU_DEFAULT['avatar'];
      else {
        // Valida si la imagen está disponible.
        if (file_exists(NABU_DIRECTORY['storage_avatars'] . '/' . $user['avatar']))
          $user['avatar'] = NABU_DIRECTORY['avatars'] . '/' . $user['avatar'];
        else
          $user['avatar'] = NABU_DEFAULT['avatar'];
      }

      // Defina la ruta completa del fondo de perfil.
      if (empty($user['background']))
        $user['background'] = NABU_DEFAULT['background'];
      else {
        // Valida si la imagen está disponible.
        if (file_exists(NABU_DIRECTORY['storage_backgrounds'] . '/' . $user['background']))
          $user['background'] = NABU_DIRECTORY['backgrounds'] . '/' . $user['background'];
        else
          $user['background'] = NABU_DEFAULT['background'];
      }

      $is_my_profile = false;

      // Muestra el botón de edición de perfil únicamente en el perfil del usuario.
      if (utils::session_exists())
        if ($_SESSION['user']['username'] == $_GET['user'])
          $is_my_profile = true;

      require_once 'views/layouts/head.php';
      require_once 'views/layouts/navbar.php';
      require_once 'views/pages/profile.php';
      require_once 'views/layouts/footer.php';
    }
  }

  // Renderiza la página web para editar un perfil de usuario.
  public static function edit_profile() {
    if (!utils::session_exists())
      self::default_route();

    if (empty($_POST['edit_profile_submit'])) {
      $userModel = new userModel();

      // Busca los datos de perfil del usuario de sesión.
      $user = $userModel -> find_profile('id', $_SESSION['user']['id']);

      unset($userModel);

      if ($user === false)
        self::default_route();

      $head_title = 'Editar perfil';
      $token      = csrf::generate_token();
      $messages   = $GLOBALS['messages'] -> messages();

      // Defina la ruta completa de la foto de perfil.
      if (empty($user['avatar']))
        $user['avatar'] = NABU_DEFAULT['avatar'];
      else {
        // Valida si la imagen está disponible.
        if (file_exists(NABU_DIRECTORY['storage_avatars'] . '/' . $user['avatar']))
          $user['avatar'] = NABU_DIRECTORY['avatars'] . '/' . $user['avatar'];
        else
          $user['avatar'] = NABU_DEFAULT['avatar'];
      }

      // Defina la ruta completa del fondo de perfil.
      if (empty($user['background']))
        $user['background'] = NABU_DEFAULT['background'];
      else {
        // Valida si la imagen está disponible.
        if (file_exists(NABU_DIRECTORY['storage_backgrounds'] . '/' . $user['background']))
          $user['background'] = NABU_DIRECTORY['backgrounds'] . '/' . $user['background'];
        else
          $user['background'] = NABU_DEFAULT['background'];
      }

      if (empty($user['description']))
        $user['description'] = '';

      require_once 'views/layouts/head.php';
      require_once 'views/layouts/navbar.php';
      require_once 'views/pages/edit_profile.php';
      require_once 'views/layouts/footer.php';
    }
    else {
      csrf::validate_token($_POST['csrf']);

      self::update_profile($_POST);
    }
  }

  // Renderiza la página web para eliminar un perfil de usuario.
  private static function delete_profile() {
    if (!utils::session_exists())
      self::default_route();

    if (empty($_POST['confirm_password_submit'])) {
      $head_title = 'Eliminar cuenta';
      $token      = csrf::generate_token();
      $messages   = $GLOBALS['messages'] -> messages();
      $subtitle   = 'Escribe tu contraseña para eliminar tu cuenta en ' . NABU_DEFAULT['name'];
      $view       = NABU_ROUTES['delete_profile'];

      require_once 'views/layouts/head.php';
      require_once 'views/layouts/navbar.php';
      require_once 'views/pages/confirm_password.php';
      require_once 'views/layouts/footer.php';
    }
    else {
      csrf::validate_token($_POST['csrf']);

      self::delete_account($_POST);
    }
  }

  // Valida los campos del formulario de registro de usuarios
  // y registra un nuevo usuario.
  private static function user_signup($form) {
    $validations = new validations(NABU_ROUTES['signup']);

    $data = $validations -> validate_form($form, array(
      array('name',     'exist' => true, 'trim_all' => true, 'min'        => 5),
      array('username', 'exist' => true, 'trim_all' => true, 'not_spaces' => true),
      array('email',    'exist' => true, 'trim_all' => true, 'min'        => 5, 'is_email' => true, 'not_spaces' => true),
      array('password', 'exist' => true, 'trim'     => true, 'min'        => 6, 'equal'    => array(true, $form['password_confirm']), 'not_spaces' => true)
    ));

    // Formatea en minúsculas la dirección de e-mail.
    $data['email'] = strtolower($data['email']);

    $userModel = new userModel();

    // Busca si el apodo y la dirección de e-mail son únicos.
    $users = $userModel -> find_user($data['username'], $data['email']);

    global $messages;

    // Elimina cuentas de usuario si la cuenta está inactiva y con token expirado.
    foreach ($users as $user) {
      // Existe una cuenta activa.
      if ($user['activated'] && empty($user['token_expiration'])) {
        $messages -> add_message('Existe una cuenta registrada con el mismo apodo o dirección de correo electrónico, por favor inténtalo de nuevo');

        break;
      }

      // Existe una cuenta eliminada pero con registro de datos de usuario.
      if (!$user['activated'] && empty($user['token_expiration'])) {
        $messages -> add_message('Existe una cuenta registrada con el mismo apodo o dirección de correo electrónico, por favor inténtalo de nuevo');

        break;
      }

      // Existe un usuario con cuenta inactiva.
      if (!$user['activated'] && time() > $user['token_expiration'])
        $userModel -> delete_user($user['id']);
      else {
        $messages -> add_message('Existe una cuenta registrada con el mismo apodo o dirección de correo electrónico, por favor inténtalo de nuevo');

        break;
      }
    }

    // Redirecciona al formulario de registro si el apodo o la dirección de e-mail no son únicos.
    $messages -> message_exists(NABU_ROUTES['signup']);

    // Genera una llave aleatoria de verificación de dirección de e-mail.
    $key = bin2hex(random_bytes(32));

    $emails = new emails();
    $emails -> prepare($data['email'], $data['name']);

    // Genera una URL de verificación de e-mail.
    $url_verification = NABU_ROUTES['verifications'] . '&user=' . urlencode($data['username']) . '&key=' . $key;
    $username         = utils::str_escape($data['username']);

    // Mensaje de verificación de dirección de e-mail.
    $body = require_once 'views/emails/verifications.php';

    // Envía primero la URL de verificación de dirección de e-mail antes de registrar el usuario.
    if (!$emails -> send('¡Ya casi está listo!', $body))
      self::errors('tuvimos un problema al enviar tu mensaje de verificación de e-mail');

    // Cifra la contraseña.
    $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT, self::password_cost);

    // Registra el nuevo usuario en la base de datos.
    $userModel -> save_user($data);

    $id = $userModel -> user_id('email', $data['email']);

    // Hash de verificación de e-mail.
    $hash = hash_hmac(self::function_hash, $data['email'], $key);

    // Registra el hash de verificación de dirección de e-mail en la base de datos.
    $userModel -> user_verifications($id, $hash);

    $messages -> add_message('Su cuenta se ha registrado correctamente, por favor verifica tu dirección de correo electrónico');
    $messages -> redirect(NABU_ROUTES['signup']);
  }

  // Valida los campos del formulario de inicio de sesión
  // y otorga las credenciales de acceso.
  private static function user_login($form) {
    $validations = new validations(NABU_ROUTES['login']);

    $data = $validations -> validate_form($form, array(
      array('identity', 'exist' => true, 'trim_all' => true, 'not_spaces' => true),
      array('password', 'exist' => true, 'trim'     => true, 'min'        => 6, 'not_spaces' => true)
    ));

    $column = 'username';

    // Selecciona el tipo de identificación (apodo o e-mail).
    if (filter_var($data['identity'], FILTER_VALIDATE_EMAIL))
      $column = 'email';

    $userModel = new userModel();

    // Busca el usuario de acceso.
    $user = $userModel -> user_access($column, $data['identity']);

    global $messages;

    if ($user) {
      // Valida la contraseña del usuario y define las credenciales de acceso.
      if (password_verify($data['password'], $user['password'])) {
        // Cuenta activa.
        if ($user['activated'] && empty($user['token_expiration'])) {
          $_SESSION['user'] = array(
            'id'       => $user['id'],
            'username' => $user['username'],
            'role'     => $user['role']
          );
        }
        // Cuenta eliminada pero con registro de datos de usuario.
        elseif (!$user['activated'] && empty($user['token_expiration']))
          $messages -> add_message('La identificación de sesión o la contraseña es incorrecta');
        // Cuenta inactiva y con token expirado; elimina su cuenta.
        elseif (!$user['activated'] && time() > $user['token_expiration']) {
          $userModel -> delete_user($user['id']);

          $messages -> add_message('Tu cuenta ha expirado, por favor vuelva a registrarse.');
        }
        else
          $messages -> add_message('Por favor confirma tu dirección de correo electrónico');
      }
      else
        $messages -> add_message('La identificación de sesión o la contraseña es incorrecta');
    }
    else
      $messages -> add_message('La identificación de sesión o la contraseña es incorrecta');

    // Redirecciona al formulario de inicio de sesión si las credenciales de acceso no coinciden.
    $messages -> message_exists(NABU_ROUTES['login']);

    // Redirecciona al panel de administración de acuerdo al rol.
    if (utils::is_admin())
      $messages -> redirect(NABU_ROUTES['admin']);

    // Redirecciona al perfil del usuario.
    $messages -> redirect(NABU_ROUTES['profile'] . '&user=' . urlencode($user['username']));
  }

  // Valida los campos del formulario de actualización de perfil
  // y actualiza un perfil de usuario.
  private static function update_profile($form) {
    $form = array_merge($form, $_FILES);

    $validations = new validations(NABU_ROUTES['edit_profile']);

    // Valida los campos del formulario de edición de perfil.
    $data = $validations -> validate_form($form, array(
      array('avatar',      'type'     => 'image', 'max' => NABU_DEFAULT['image_size']),
      array('background',  'type'     => 'image', 'max' => NABU_DEFAULT['image_size']),
      array('description', 'trim_all' => true),
      array('name',        'trim_all' => true, 'min'        => 5),
      array('username',    'trim_all' => true, 'not_spaces' => true),
      array('password',    'trim'     => true, 'min'        => 6, 'equal' => array(true, $form['password_confirm']), 'not_spaces' => true)
    ));

    global $messages;

    if (empty($data))
      $messages -> redirect(NABU_ROUTES['edit_profile']);

    // Obtiene los datos de perfil actual del usuario.
    $userModel = new userModel();
    $id        = $_SESSION['user']['id'];
    $user      = $userModel -> find_profile('id', $id);

    if ($user === false)
      $messages -> redirect(NABU_ROUTES['logout']);

    $update = array();

    // Valida si hay cambios en el nombre.
    if (!empty($data['name'])) {
      if ($data['name'] != $user['name']) {
        $update['name'] = $data['name'];

        $messages -> add_message('Tu nombre completo se ha actualizado correctamente');
      }
    }

    // Valida si hay cambios en el apodo.
    if (!empty($data['username'])) {
      if ($data['username'] != $user['username']) {
        $username = $userModel -> user_id('username', $data['username']);

        // Valida si el apodo es único.
        if ($username)
          $messages -> add_message('Por favor define un apodo diferente');
        else {
          $update['username'] = $data['username'];

          $messages -> add_message('Tu apodo se ha actualizado correctamente');
        }

        unset($username);
      }
    }

    // Valida si hay cambios en la contraseña.
    if (!empty($data['password'])) {
      $update['password'] = password_hash($data['password'], PASSWORD_DEFAULT, self::password_cost);

      $messages -> add_message('Tu contraseña se ha actualizado correctamente');
    }

    // Actualiza los datos del usuario en la base de datos.
    if (!empty($update)) {
      $userModel -> update_user($id, $update);

      // Actualiza la variable de sesión.
      if (!empty($update['username']))
        $_SESSION['user']['username'] = $update['username'];

      $update = array();
    }

    // Valida si hay cambios en la descripción del perfil.
    if (empty($data['description'])) {
      if ($user['description']) {
        $update['description'] = null;

        $messages -> add_message('Tu descripción se ha actualizado correctamente');
      }
    }
    else
      if ($data['description'] != $user['description']) {
        $update['description'] = $data['description'];

        $messages -> add_message('Tu descripción se ha actualizado correctamente');
      }

    // Valida si hay cambios en la foto de perfil.
    if (!empty($data['avatar'])) {
      $extension = explode('/', $data['avatar']['type'])[1];

      if ($extension == 'svg+xml')
        $extension = 'svg';

      // Valida si el nombre del archivo de la imagen es único.
      do
        $filename = bin2hex(random_bytes(32)) . '.' . $extension;
      while ($userModel -> find_image('avatar', $filename));

      $destination = NABU_DIRECTORY['storage_avatars'] . '/' . $filename;

      // Mueve la imagen subida a la capeta de almacenamiento de fotos de perfil.
      if (move_uploaded_file($data['avatar']['tmp_name'], $destination)) {
        $update['avatar'] = $filename;

        // Remueve la imagen anterior.
        if (!empty($user['avatar'])) {
          $avatar = NABU_DIRECTORY['storage_avatars'] . '/' . $user['avatar'];

          if (file_exists($avatar))
            unlink($avatar);

          unset($avatar);
        }

        $messages -> add_message('Tu foto de perfil se ha actualizado correctamente');
      }
      else
        $messages -> add_message('¡Lo sentimos mucho! &#x1F61E;, por el momento no podemos actualizar tu foto de perfil');

      unset($extension, $filename, $destination);
    }

    // Valida si hay cambios en el fondo de perfil.
    if (!empty($data['background'])) {
      $extension = explode('/', $data['background']['type'])[1];

      if ($extension == 'svg+xml')
        $extension = 'svg';

      // Valida si el nombre del archivo de la imagen es único.
      do
        $filename = bin2hex(random_bytes(32)) . '.' . $extension;
      while ($userModel -> find_image('background', $filename));

      $destination = NABU_DIRECTORY['storage_backgrounds'] . '/' . $filename;

      // Mueve la imagen subida a la capeta de almacenamiento de fondos de perfil.
      if (move_uploaded_file($data['background']['tmp_name'], $destination)) {
        $update['background'] = $filename;

        // Remueve la imagen anterior.
        if (!empty($user['background'])) {
          $background = NABU_DIRECTORY['storage_backgrounds'] . '/' . $user['background'];

          if (file_exists($background))
            unlink($background);

          unset($background);
        }

        $messages -> add_message('El fondo de tu perfil se ha actualizado correctamente');
      }
      else
        $messages -> add_message('¡Lo sentimos mucho! &#x1F61E;, por el momento no podemos actualizar el fondo de tu perfil');

      unset($extension, $filename, $destination);
    }

    // Actualiza los datos del perfil en la base de datos.
    if (!empty($update))
      $userModel -> update_profile($_SESSION['user']['id'], $update);

    $messages -> redirect(NABU_ROUTES['edit_profile']);
  }

  // Elimina una cuenta de usuario.
  private static function delete_account($form) {
    $validations = new validations(NABU_ROUTES['delete_profile']);

    // Valida los campos del formulario de eliminar cuenta.
    $data = $validations -> validate_form($form, array(
      array('password', 'exist' => true, 'trim' => true, 'min' => 6, 'equal' => array(true, $form['password_confirm']), 'not_spaces' => true)
    ));

    $userModel = new userModel();

    $id = $_SESSION['user']['id'];

    // Obtiene el hash de la contraseña del usuario.
    $user = $userModel -> find_password($id);

    global $messages;

    if ($user === false)
      $messages -> redirect(NABU_ROUTES['logout']);

    // Valida la contraseña del usuario.
    if (password_verify($data['password'], $user['password'])) {
      // Elimina la foto de perfil.
      if (!empty($user['avatar'])) {
        $avatar = NABU_DIRECTORY['storage_avatars'] . '/' . $user['avatar'];

        if (file_exists($avatar))
          unlink($avatar);
      }

      // Elimina el fondo de perfil.
      if (!empty($user['background'])) {
        $background = NABU_DIRECTORY['storage_backgrounds'] . '/' . $user['background'];

        if (file_exists($background))
          unlink($background);
      }

      // Elimina el perfil y desactiva la cuenta del usuario.
      $userModel -> delete_profile($id);
      $userModel -> delete_account($id);
    }
    else
      $messages -> add_message('La contraseña es incorrecta');

    // Redirecciona al formulario de eliminar cuenta si las contraseñas no coinciden.
    $messages -> message_exists(NABU_ROUTES['delete_profile']);

    self::logout();
  }
}
